// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zenbase/zenbase.h>
#include <stdexcept>

#ifndef ZEN_WITH_TESTS
#	define ZEN_WITH_TESTS 1
#endif

#if ZEN_ARCH_ARM64
// On ARM we can't do this because the executable will require jumps larger
// than the branch instruction can handle. Clang will only generate
// the trampolines in the .text segment of the binary. If the zcold segment
// is present it will generate code that it cannot link.
#	define ZEN_DEBUG_SECTION
#else
// We'll put all assert implementation code into a separate section in the linked
// executable. This code should never execute so using a separate section keeps
// it well off the hot path and hopefully out of the instruction cache. It also
// facilitates reasoning about the makeup of a compiled/linked binary.
#	define ZEN_DEBUG_SECTION ZEN_CODE_SECTION(".zcold")
#endif

namespace zen {

struct CallstackFrames;

class AssertException : public std::logic_error
{
public:
	using _Mybase = std::logic_error;

	virtual ~AssertException() noexcept;

	inline AssertException(const char* Msg, struct CallstackFrames* Callstack) noexcept : _Mybase(Msg), _Callstack(Callstack) {}

	AssertException(const AssertException& Rhs) noexcept;

	AssertException(AssertException&& Rhs) noexcept;

	AssertException& operator=(const AssertException& Rhs) noexcept;

	std::string FullDescription() const noexcept;

	struct CallstackFrames* _Callstack = nullptr;
};

struct CallstackFrames;

struct AssertImpl
{
	ZEN_FORCENOINLINE ZEN_DEBUG_SECTION AssertImpl();
	virtual ZEN_FORCENOINLINE			ZEN_DEBUG_SECTION ~AssertImpl();

	static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ExecAssert
		[[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg);

	virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
	OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, CallstackFrames* Callstack);

protected:
	static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ThrowAssertException
		[[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, CallstackFrames* Callstack);
	static AssertImpl* CurrentAssertImpl;
	static AssertImpl  DefaultAssertImpl;
	AssertImpl*		   NextAssertImpl = nullptr;
};

}  // namespace zen

#define ZEN_ASSERT(x, ...)                                                 \
	do                                                                     \
	{                                                                      \
		if (x) [[unlikely]]                                                \
			break;                                                         \
		zen::AssertImpl::ExecAssert(__FILE__, __LINE__, __FUNCTION__, #x); \
	} while (false)

#ifndef NDEBUG
#	define ZEN_ASSERT_SLOW(x, ...)                                            \
		do                                                                     \
		{                                                                      \
			if (x) [[unlikely]]                                                \
				break;                                                         \
			zen::AssertImpl::ExecAssert(__FILE__, __LINE__, __FUNCTION__, #x); \
		} while (false)
#else
#	define ZEN_ASSERT_SLOW(x, ...)
#endif

//////////////////////////////////////////////////////////////////////////

#define ZEN_NOT_IMPLEMENTED(...) ZEN_ASSERT(false, __VA_ARGS__)
#define ZENCORE_API	 // Placeholder to allow DLL configs in the future (maybe)

namespace zen {

ZENCORE_API bool IsApplicationExitRequested();
ZENCORE_API bool RequestApplicationExit(int ExitCode);
ZENCORE_API int	 ApplicationExitCode();
ZENCORE_API bool IsDebuggerPresent();
ZENCORE_API void SetIsInteractiveSession(bool Value);
ZENCORE_API bool IsInteractiveSession();

ZENCORE_API void zencore_forcelinktests();

}  // namespace zen

//////////////////////////////////////////////////////////////////////////

#ifndef ZEN_USE_MIMALLOC
#	if ZEN_ARCH_ARM64
   // The vcpkg mimalloc port doesn't support Arm targets
#		define ZEN_USE_MIMALLOC 0
#	else
#		define ZEN_USE_MIMALLOC 1
#	endif
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef ZEN_WITH_TRACE
#	define ZEN_WITH_TRACE 0
#endif

//////////////////////////////////////////////////////////////////////////

using ThreadId_t = uint32_t;
